//
//  TestSpeed.m
//  LogicSim
//
//  Created by Jeremy Pereira on 10/04/2012.
//  Copyright (c) 2012 Jeremy Pereira. All rights reserved.
//

#import "TestSpeed.h"
#import "LGSEdge.h"
#import "LGSExecutor.h"
#import "LGSXorGate.h"
#import "LGSAndGate.h"
#import "LGSSplitter.h"
#import "LGSOrGate.h"
#import "LGSModule.h"
#import "LGSNotGate.h"
#import "LGSName.h"

@interface TestSpeed()

-(NSSet*) makeFullAdderA: (LGSEdge*) a 
                       b: (LGSEdge*) b 
                 carryIn: (LGSEdge*) carryIn 
                carryOut: (LGSEdge*) carryOut
                     bit: (LGSEdge*) bit
                   index: (unsigned int) index;

@end

@implementation TestSpeed

-(void) testFullAdder
{
    LGSEdge* a = [[LGSEdge alloc] init];
    LGSEdge* b = [[LGSEdge alloc] init];
    LGSEdge* carryIn = [[LGSEdge alloc] init];
    LGSEdge* carryOut = [[LGSEdge alloc] init];
    LGSEdge* bit = [[LGSEdge alloc] init];
    
	NSSet* fullAdderNodes = [self makeFullAdderA: a 
                                               b: b 
                                         carryIn: carryIn 
                                        carryOut: carryOut 
                                             bit: bit
                                           index: 0];
    NSLog(@"Adder nodes %@", fullAdderNodes);
    NSMutableOrderedSet* nodesToEvaluate = [[NSMutableOrderedSet alloc] init];
    LGSExecutor* executor = [[LGSExecutor alloc] init];
    [executor setTrace: true];
    /*
     * 0 + 0 + 0 = 0 carry 0
     */
    [a setValue: false affectedNodes: nodesToEvaluate];
    [b setValue: false affectedNodes: nodesToEvaluate];
    [carryIn setValue: false affectedNodes: nodesToEvaluate];
    unsigned long iterations = [executor evaluateWithInitialSet: nodesToEvaluate 
                                                  maxIterations: 100];
    STAssertTrue(iterations < 100, @"Overrun iterations");
    STAssertFalse([bit value], @"bit should be false");
    STAssertFalse([carryOut value], @"Carry out should be false");
    /*
     * 1 + 0 + 0 = 1 carry 0
     */
    [a setValue: true affectedNodes: nodesToEvaluate];
    iterations = [executor evaluateWithInitialSet: nodesToEvaluate 
                                    maxIterations: 100];
    STAssertTrue(iterations < 100, @"Overrun iterations");
    STAssertTrue([bit value], @"Bit should be true");
    STAssertFalse([carryOut value], @"Carry out should be false");
    
    /*
     * 1 + 1 + 0 = 0 carry 1
     */
    [b setValue: true affectedNodes: nodesToEvaluate];
    iterations = [executor evaluateWithInitialSet: nodesToEvaluate 
                                    maxIterations: 100];
    STAssertTrue(iterations < 100, @"Overrun iterations");
    STAssertFalse([bit value], @"Bit should be false");
    STAssertTrue([carryOut value], @"Carry out should be true");
    /*
     * 1 + 0 + 1 = 0 carry 1
     */
    [b setValue: false affectedNodes: nodesToEvaluate];
    [carryIn setValue: true affectedNodes: nodesToEvaluate];
    iterations = [executor evaluateWithInitialSet: nodesToEvaluate 
                                    maxIterations: 100];
    STAssertTrue(iterations < 100, @"Overrun iterations");
    STAssertFalse([bit value], @"Bit should be false");
    STAssertTrue([carryOut value], @"Carry out should be true");
    
    /*
     * 1 + 1 + 1 = 0 carry 1
     */
    [b setValue: true affectedNodes: nodesToEvaluate];
    [carryIn setValue: true affectedNodes: nodesToEvaluate];
    iterations = [executor evaluateWithInitialSet: nodesToEvaluate 
                                    maxIterations: 100];
    STAssertTrue(iterations < 100, @"Overrun iterations");
    STAssertTrue([bit value], @"Bit should be true");
    STAssertTrue([carryOut value], @"Carry out should be true");

   
}

-(void) test32BitAdd
{
    NSMutableArray* aIn = [[NSMutableArray alloc] init];
    NSMutableArray* bIn = [[NSMutableArray alloc] init];
    NSMutableArray* out = [[NSMutableArray alloc] init];
    NSMutableSet* theGates = [[NSMutableSet alloc] init];
    
    LGSEdge* carryIn = [[LGSEdge alloc] init];
    LGSEdge* lastCarry = carryIn;
    for (unsigned int i = 0 ; i < 32 ; ++i)
    {
        LGSEdge* a = [[LGSEdge alloc] init];
        LGSEdge* b = [[LGSEdge alloc] init];
        LGSEdge* bit = [[LGSEdge alloc] init];
        LGSEdge* carryOut = [[LGSEdge alloc] init];
        NSSet* gatesToAdd = [self makeFullAdderA: a 
                                               b: b 
                                         carryIn: lastCarry  
                                        carryOut: carryOut 
                                             bit: bit
                                           index: i];
        [theGates unionSet: gatesToAdd];
        [aIn addObject: a];
        [bIn addObject: b];
        [out addObject: bit];
        lastCarry = carryOut;
    }
    LGSExecutor* executor = [[LGSExecutor alloc] init];
    NSMutableOrderedSet* nodesToEvaluate = [[NSMutableOrderedSet alloc] init];
    [LGSEdge setArray: aIn 
           from32Bits: 0x1F34567F 
        affectedNodes: nodesToEvaluate];
    [LGSEdge setArray: bIn 
           from32Bits: 0x2f45678F 
        affectedNodes: nodesToEvaluate];
 	[carryIn setValue: false affectedNodes: nodesToEvaluate];
    [executor setTrace: false];
	unsigned long iterations = [executor evaluateWithInitialSet: nodesToEvaluate 
                                                  maxIterations: 1000];
    NSLog(@"Iterations: %lu", iterations);
    uint32_t result = [LGSEdge make32BitNumberFromEdgeArray: out];
    STAssertTrue(result == 0x1f34567F + 0x2f45678F, 
                 @"Invalid result %x", 
                 result);
    
}

-(void) testPlus
{
    NSLog(@"%@", [@"+" stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]);
}


-(NSSet*) makeFullAdderA: (LGSEdge*) a 
                       b: (LGSEdge*) b 
                 carryIn: (LGSEdge*) carryIn 
                carryOut: (LGSEdge*) carryOut
                     bit: (LGSEdge*) bit
                   index: (unsigned int) index
{
    LGSSplitter* aSplitter = [[LGSSplitter alloc] init];
    [aSplitter setName: [LGSName nameWithString: [NSString stringWithFormat: @"a[%u]", index]]];
    LGSSplitter* bSplitter 
    	= [[LGSSplitter alloc] init];
    [bSplitter setName: [LGSName nameWithString: [NSString stringWithFormat: @"b[%u]", index]]];
    LGSSplitter* carryInSplitter 
    	= [[LGSSplitter alloc] init];
    [carryInSplitter setName: [LGSName nameWithString: [NSString stringWithFormat: @"cin[%u]", index]]];
    [aSplitter addInput: a forNameString: @"in"];
    [bSplitter addInput: b forNameString: @"in"];
    [carryInSplitter addInput: carryIn forNameString: @"in"];
    LGSXorGate* aXorB = [[LGSXorGate alloc] init];
    [aXorB setName: [LGSName nameWithString: [NSString stringWithFormat: @"aXorB[%u]", index]]];
    [aSplitter connect: aXorB fromString: @"out" toString: @"a"];
    [bSplitter connect: aXorB fromString: @"out" toString: @"b"];
    
    LGSXorGate* aXorBXorC = [[LGSXorGate alloc] init];
    [aXorBXorC setName: [LGSName nameWithString: [NSString stringWithFormat: @"aXorBXorC[%u]", index]]];
    [aXorB connect: aXorBXorC fromString: @"output" toString: @"a"];
    [carryInSplitter connect: aXorBXorC fromString: @"out" toString: @"b"];
    [aXorBXorC addOutput: bit forNameString: @"output"];
    
    LGSAndGate* aAndB = [[LGSAndGate alloc] init ];
	[aAndB setName: [LGSName nameWithString: [NSString stringWithFormat: @"aAndB[%u]", index]]];
    [aSplitter connect: aAndB fromString: @"out" toString: @"a"];
    [bSplitter connect: aAndB fromString: @"out" toString: @"b"];
    LGSAndGate* aXorBandC = [[LGSAndGate alloc] init];
    [aXorBandC setName:[LGSName nameWithString: [NSString stringWithFormat: @"aXorBAndC[%u]", index]]];
    [aXorB connect: aXorBandC fromString: @"output" toString: @"a"];
    [carryInSplitter connect: aXorBandC fromString: @"out" toString: @"b"];
    LGSOrGate* carryOutOr = [[LGSOrGate alloc] init];
    [carryOutOr setName:[LGSName nameWithString: [NSString stringWithFormat: @"cout[%u]", index]]];
    [aAndB connect: carryOutOr fromString: @"output" toString: @"a"];
    [aXorBandC connect: carryOutOr fromString: @"output" toString: @"b"];
    [carryOutOr addOutput: carryOut forNameString: @"output"];
    
    NSSet* gates = [NSSet setWithObjects: aSplitter, bSplitter, carryInSplitter, 
                    					  aXorB, aXorBXorC, 
                    					  aXorBandC, aAndB, carryOutOr, nil];
    return gates;
}

-(void) testMakeModule
{
    LGSEdge* carryIn = [[LGSEdge alloc] init];
    LGSEdge* a = [[LGSEdge alloc] init];
    LGSEdge* b = [[LGSEdge alloc] init];
    LGSEdge* bit = [[LGSEdge alloc] init];
    LGSEdge* carryOut = [[LGSEdge alloc] init];
    LGSModule* module = [[LGSModule alloc] initWithNameString: @"adder"];
    
    LGSXorGate* aXorB = [[LGSXorGate alloc] initWithNameString: @"a xor b"];
    LGSXorGate* aXorBXorC = [[LGSXorGate alloc] initWithNameString: @"(a xor b) xor c"];
    LGSAndGate* aAndB = [[LGSAndGate alloc] initWithNameString: @"a & b"];
    LGSAndGate* aXorBAndC = [[LGSAndGate alloc] initWithNameString: @"(a xor b) & c"];
    LGSOrGate* aAndBOrAXorBAndC = [[LGSOrGate alloc] initWithNameString: @"(a & b) | ((a xor b) & c)"];
    NSSet* adderNodes = [NSSet setWithObjects: aXorB,
                         					   aXorBXorC,
                                               aAndB,
                                               aXorBAndC,
                         					   aAndBOrAXorBAndC,
                         					   nil];
    
    [module addNodesFromSet: adderNodes];
    [module setInputNames: @[[LGSName nameWithConnectionString: @"a"],
                             [LGSName nameWithConnectionString: @"b"],
                             [LGSName nameWithConnectionString: @"cin"]]];
    [module setOutputNames: @[[LGSName nameWithConnectionString: @"bit"],
                              [LGSName nameWithConnectionString: @"cout"]]];
    [aXorB connect: aXorBXorC fromString: @"output" toString: @"a"];
    [aXorB connect: aXorBAndC fromString: @"output" toString: @"a"];
    [aAndB connect: aAndBOrAXorBAndC fromString: @"output" toString: @"a"];
    [aXorBAndC connect: aAndBOrAXorBAndC fromString: @"output" toString: @"b"];
    [module setInputMappingFromString: @"a"
                            toStrings: @[@"a xor b.a", @"a & b.a"]];
    [module setInputMappingFromString: @"b"
                            toStrings: @[@"a xor b.b", @"a & b.b"]];
    [module setInputMappingFromString: @"cin"
                            toStrings: @[@"(a xor b) xor c.b", @"(a xor b) & c.b"]];
    [module addOutputMappingFromString: @"bit"
                              toString: @"(a xor b) xor c.output"];
    [module addOutputMappingFromString: @"cout"
                              toString: @"(a & b) | ((a xor b) & c).output"];
    [module addInput: a forNameString: @"a"];
    [module addInput: b forNameString: @"b"];
    [module addInput: carryIn forNameString: @"cin"];
    [module addOutput: bit forNameString: @"bit"];
    [module addOutput: carryOut forNameString: @"cout"];
    
    NSMutableOrderedSet* nodesToEvaluate = [[NSMutableOrderedSet alloc] init];
    LGSExecutor* executor = [[LGSExecutor alloc] init];
    [executor setTrace: true];
    /*
     * 0 + 0 + 0 = 0 carry 0
     */
    [a setValue: false affectedNodes: nodesToEvaluate];
    [b setValue: false affectedNodes: nodesToEvaluate];
    [carryIn setValue: false affectedNodes: nodesToEvaluate];
    unsigned long iterations = [executor evaluateWithInitialSet: nodesToEvaluate 
                                                  maxIterations: 100];
    STAssertTrue(iterations < 100, @"Overrun iterations");
    STAssertFalse([bit value], @"bit should be false");
    STAssertFalse([carryOut value], @"Carry out should be false");
    /*
     * 1 + 0 + 0 = 1 carry 0
     */
    [a setValue: true affectedNodes: nodesToEvaluate];
    iterations = [executor evaluateWithInitialSet: nodesToEvaluate 
                                    maxIterations: 100];
    STAssertTrue(iterations < 100, @"Overrun iterations");
    STAssertTrue([bit value], @"Bit should be true");
    STAssertFalse([carryOut value], @"Carry out should be false");
    
    /*
     * 1 + 1 + 0 = 0 carry 1
     */
    [b setValue: true affectedNodes: nodesToEvaluate];
    iterations = [executor evaluateWithInitialSet: nodesToEvaluate 
                                    maxIterations: 100];
    STAssertTrue(iterations < 100, @"Overrun iterations");
    STAssertFalse([bit value], @"Bit should be false");
    STAssertTrue([carryOut value], @"Carry out should be true");
    /*
     * 1 + 0 + 1 = 0 carry 1
     */
    [b setValue: false affectedNodes: nodesToEvaluate];
    [carryIn setValue: true affectedNodes: nodesToEvaluate];
    iterations = [executor evaluateWithInitialSet: nodesToEvaluate 
                                    maxIterations: 100];
    STAssertTrue(iterations < 100, @"Overrun iterations");
    STAssertFalse([bit value], @"Bit should be false");
    STAssertTrue([carryOut value], @"Carry out should be true");
    
    /*
     * 1 + 1 + 1 = 0 carry 1
     */
    [b setValue: true affectedNodes: nodesToEvaluate];
    [carryIn setValue: true affectedNodes: nodesToEvaluate];
    iterations = [executor evaluateWithInitialSet: nodesToEvaluate 
                                    maxIterations: 100];
    STAssertTrue(iterations < 100, @"Overrun iterations");
    STAssertTrue([bit value], @"Bit should be true");
    STAssertTrue([carryOut value], @"Carry out should be true");
}

-(void) testSRLatch
{
    LGSEdge* s = [[LGSEdge alloc] init];
    LGSEdge* r = [[LGSEdge alloc] init];
    LGSEdge* q = [[LGSEdge alloc] init];
    LGSEdge* notQ = [[LGSEdge alloc] init];
    
    LGSOrGate* rOrNotQ = [[LGSOrGate alloc] initWithNameString: @"r | ~q"];
    LGSOrGate* sOrQ = [[LGSOrGate alloc] initWithNameString: @"s | q"];
    LGSNotGate* notRorNotQ = [[LGSNotGate alloc] initWithNameString:  @"~(r | ~q)"];
    LGSNotGate* notSorQ =[[LGSNotGate alloc] initWithNameString: @"~(s | q)"];
    
    [rOrNotQ addInput: r forNameString: @"a"];
    [notSorQ connect: rOrNotQ fromString: @"out" toString: @"b"];
    [rOrNotQ connect: notRorNotQ fromString: @"output" toString: @"in"];
    [notRorNotQ addOutput: q forNameString: @"out"];
    
    [sOrQ addInput: s forNameString: @"b"];
    [notRorNotQ connect: sOrQ fromString: @"out" toString: @"a"];
    [sOrQ connect: notSorQ fromString: @"output" toString: @"in"];
    [notSorQ addOutput: notQ forNameString: @"out"];
    
    LGSExecutor* executor = [[LGSExecutor alloc] init];
    [executor setTrace: true];
    NSMutableOrderedSet* nodesToEvaluate = [[NSMutableOrderedSet alloc] init];
    /*
     *  Set the flip flop into an initially valid state
     */
    LGSEdge* anEdge = [notSorQ input: [[notSorQ class] inputName]];
    [anEdge setValue: true affectedNodes: nodesToEvaluate];
    [anEdge setValue: false affectedNodes: nodesToEvaluate];
    [s setValue: true affectedNodes: nodesToEvaluate];
    size_t iterations = [executor evaluateWithInitialSet: nodesToEvaluate
                                           maxIterations: 100];
    NSLog(@"q = %d, ~q = %d", (int)[q value], (int) [notQ value]);
    STAssertTrue([q value] && ![notQ value], @"Flip flop state incorrect");
    [s setValue: false affectedNodes: nodesToEvaluate];
    iterations += [executor evaluateWithInitialSet: nodesToEvaluate 
                                	 maxIterations: 100];
    NSLog(@"q = %d, ~q = %d", (int)[q value], (int) [notQ value]);
    STAssertTrue([q value] && ![notQ value], @"Flip flop state incorrect");
    [s setValue: true affectedNodes: nodesToEvaluate];
    iterations += [executor evaluateWithInitialSet: nodesToEvaluate 
                                	 maxIterations: 100];
    NSLog(@"q = %d, ~q = %d", (int)[q value], (int) [notQ value]);
    STAssertTrue([q value] && ![notQ value], @"Flip flop state incorrect");
    [s setValue: false affectedNodes: nodesToEvaluate];
    [r setValue: true affectedNodes: nodesToEvaluate];
    iterations += [executor evaluateWithInitialSet: nodesToEvaluate 
                                     maxIterations: 100];
    NSLog(@"q = %d, ~q = %d", (int)[q value], (int) [notQ value]);
    STAssertTrue(![q value] && [notQ value], @"Flip flop state incorrect");
    [r setValue: false affectedNodes: nodesToEvaluate];
    iterations += [executor evaluateWithInitialSet: nodesToEvaluate 
                                     maxIterations: 100];
    NSLog(@"q = %d, ~q = %d", (int)[q value], (int) [notQ value]);
    STAssertTrue(![q value] && [notQ value], @"Flip flop state incorrect");
    NSLog(@"Iterations %lu", iterations);
}

-(void) testGateDelay
{
    LGSSplitter* splitter = [[LGSSplitter alloc] init];
    LGSOrGate* orGate = [[LGSOrGate alloc] init];
    LGSNotGate* notGate = [[LGSNotGate alloc] init];
    LGSEdge* input = [[LGSEdge alloc] init];
    [splitter addInput: input forNameString: @"in"];
    [splitter connect: notGate fromString: @"out" toString: @"in"];
    LGSExecutor* executor = [[LGSExecutor alloc] init];
    [executor setTrace: true];
    NSMutableOrderedSet* initialGates = [[NSMutableOrderedSet alloc] init];
    [input setValue: true affectedNodes: initialGates];
    /*
     *  Test that the splitter has a gate delay of 0
     */
    [executor evaluateWithInitialSet: initialGates maxIterations: 1];
    STAssertTrue([executor nodeCountAtTick: 0] == 1, @"Splitter does not have zero gate delay");
	/*
     *  Now test that an or gate has a gate delay of 1
     */
    [initialGates removeAllObjects];
    [executor removeAllNodes];
    LGSEdge* input2 = [[LGSEdge alloc] init];
    [input setValue: false affectedNodes: nil];
    [input2 setValue: false affectedNodes: nil];
    [notGate removeInputs];
    [orGate addInput: input forNameString: @"a"];
    [orGate addInput: input2 forNameString: @"b"];
    [orGate connect: notGate fromString: @"output" toString: @"in"];
    [input setValue: true affectedNodes: initialGates];
    [initialGates addObject: splitter]; // make sure the time 0 gates doesn't empty
    [executor evaluateWithInitialSet: initialGates maxIterations: 1];
    STAssertTrue([executor nodeCountAtTick: 1] == 1, @"Or does not have gate delay of 1");
    
}

-(void) testAddInput
{
    LGSOrGate* a = [[LGSOrGate alloc] initWithNameString: @"a"];
    LGSSplitter* b = [[LGSSplitter alloc] initWithNameString: @"b"];
    LGSModule* module = [[LGSModule alloc] initWithNameString: @"module"];
    [module setInputNames: @[[LGSName nameWithConnectionString: @"a"],
     						 [LGSName nameWithConnectionString: @"b"]]];
    [module addNodesFromSet: [NSSet setWithObjects: a, b, nil]];
    NSArray* aMappings = @[@"a.a", @"b.in"];
    STAssertTrue([module setInputMappingFromString: @"a" toStrings: aMappings],
                 @"Failed to add mapping for a");
    STAssertTrue([module setInputMappingFromString: @"b" toStrings: @[@"a.b"]],
                 @"Failed to add mapping for b");
    LGSEdge* aEdge = [[LGSEdge alloc] init];
    [module addInput: aEdge forNameString: @"a"];
    LGSNode* expectedSplitter = [aEdge output];
    STAssertTrue([expectedSplitter isKindOfClass: [LGSSplitter class]],
                 @"Expected a splitter %@",
                 expectedSplitter);
    NSSet* outputs = [expectedSplitter outputsForName: [[expectedSplitter class] outputName]];
    STAssertTrue([outputs count] == 2,
                 @"a splitter has wrong number of outputs %@",
                 outputs);
    for (LGSEdge* anOutputEdge in outputs)
    {
        STAssertTrue([anOutputEdge output] == a || [anOutputEdge output] == b,
                     @"Invalid internal input connection %@", outputs);
        
    }
}

-(void) testAddOutput
{
    LGSSplitter* a = [[LGSSplitter alloc] initWithNameString: @"a"];
    LGSSplitter* b = [[LGSSplitter alloc] initWithNameString: @"b"];
    LGSModule* module = [[LGSModule alloc] initWithNameString: @"module"];
    [module setOutputNames: @[[LGSName nameWithConnectionString:@"a"],
     						  [LGSName nameWithConnectionString:@"b"]]];
    [module addNodesFromSet: [NSSet setWithObjects: a, b, nil]];
    STAssertTrue([module addOutputMappingFromString: @"a"
                                           toString: @"a.out"],
                 @"Failed to add mapping for a");
    STAssertTrue([module addOutputMappingFromString: @"b"
                                           toString: @"b.out"],
                 @"Failed to add mapping for b");

    LGSEdge* aEdge = [[LGSEdge alloc] init];
    [module addOutput: aEdge forNameString: @"a"];
    STAssertEquals([aEdge input], a, @"AEdge connected wrongly: %@", aEdge);
    LGSEdge* bEdge = [[LGSEdge alloc] init];
    [module addOutput: bEdge forNameString: @"b"];
    STAssertEquals([bEdge input], b, @"bEdge connected wrongly: %@", bEdge);
}


@end
